-- AvalonCannonM
-- Created by dengc
-- 阿瓦隆巨炮模块

module("AvalonCannonM", package.seeall);

local avalonCannonList = {};
local avalonCannonSkills = {};

-- 阿瓦隆巨炮是否需要播放填弹光效
local playCannonLoadEffect = false;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    avalonCannonList = {};

    -- avalon_cannon表
    local avalon_cannon_CSV = get_avalon_cannon_CSV();
    for i = 1, #avalon_cannon_CSV do
        avalonCannonList[avalon_cannon_CSV[i].class_id] = avalon_cannon_CSV[i];

        -- 解析动作帧范围
        parseActionFrameRange(avalon_cannon_CSV[i]);
    end
    avalon_cannon_CSV = {};
end

function init()
    loadCsv();

    -- 开始新回合
    CombatM.registerEndRoundCB("avalon_cannon_start_round", refreshCd);

    -- 清空下注册的事件
    EventMgr.removeAll("AvalonCannonM");

    -- 关注开始地牢事件
    EventMgr.register("AvalonCannonM", event.START_DUNGEON, function(layer)
        whenStartDungeon();
    end);

    -- 关注离开地牢事件
    EventMgr.register("AvalonCannonM", event.LEAVE_DUNGEON, whenLeaveDungeon);

    -- 开始打包验证客户端结果
    EventMgr.register("AvalonCannonM", event.START_PACK_VERIFY_RESULT, function(para)
        syncAvalonCannonData();
    end);
end

-- 解析动作的帧范围
parseActionFrameRange = function(info)
    -- 待机、攻击、施法
    local fields = { "idle_frame", "attack_frame", "cast_frame",};

    for _, field in pairs(fields) do
        local range = info[field];
        if not string.startWith(range, "[") or not string.endWith(range, "]") then
            error(string.format("字段(%s)配置格式错误!", tostring(field)));
            return info;
        end

        local arr = string.explode(string.sub(range, 2, -2),",");
        if #arr ~= 2 then
            error(string.format("字段(%s)配置格式错误!", tostring(field)));
            return info;
        end

        info[field] = { tonumber(arr[1]), tonumber(arr[2]) };
    end

    return info;
end

-- 开始地牢
whenStartDungeon = function()
    -- 设置阿瓦隆巨炮的技能
    local skills = EngineeringManualM.fetchAvalonSkills();
    setCannonSkills(skills);
end

-- 离开地牢，直接丢弃巨炮数据
whenLeaveDungeon = function()
    ME.user.avalon_cannon = nil;
    ME.user.dbase:delete("avalon_cannon");
    avalonCannonSkills = {};
end

-- 刷新冷却时间
refreshCd = function()
    local avalonCannon = ME.user.avalon_cannon;
    if not avalonCannon then
        return;
    end

    -- 获取当前的冷却时间
    local cd = avalonCannon:getCountDown();
    if cd <= 0 then
        return;
    end

    -- 设置
    cd = cd - 1;
    avalonCannon:setCd(cd);
end

-- 开始新的一层
function whenNextFloor()
    -- 先同步下数据
    syncAvalonCannonData();

    -- 创建阿瓦隆巨炮
    local avalonCannonInfo = ME.user.dbase:query("avalon_cannon");
    if type(avalonCannonInfo) ~= "table" or type(avalonCannonInfo.class_id) ~= "number" then
        return;
    end

    local classId = avalonCannonInfo["class_id"];
    local para = AvalonCannonM.fetchAvalonCannonData(classId);
    AvalonCannonM.createAvalonCannon(classId, para);

    -- 尝试自动装填弹药
    tryLoadAmmunition();
end

-- 检索数据
function query(classId, path)
    local m = avalonCannonList[classId];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 创建阿瓦隆巨炮
function createAvalonCannon(classId, info)
    info = info or {};
    if info.skills and info.skills.array then
        info.skills = info.skills:toTable();
    end

    -- 填充必要的数据
    local dbase = table.add({
        ["class_id"] = classId,
        ["skills"]   = {},
    }, info);

    -- 生成对象，记录在user下面
    local avalonCannon = AvalonCannon.new(classId, dbase);
    ME.user.avalon_cannon = avalonCannon;

    -- 刷新下属性
    PropM.refresh(avalonCannon);

    return avalonCannon;
end

-- 能否攻击目标
function canAttack()
    -- 获取不到巨炮
    local avalonCannon = ME.user.avalon_cannon;
    if not avalonCannon then
        trace("AvalonCannonM", "获取不到巨炮。");
        return false;
    end

    -- 已经被摧毁
    if avalonCannon:isDestory() then
        trace("AvalonCannonM", "已经被摧毁。");
        return false;
    end

    -- 还处于冷却中
    local cd = avalonCannon:getCountDown();
    if cd > 0 then
        trace("AvalonCannonM", "还在冷却之中。");
        return "in_cd";
    end

    -- 没有耐久度了
    if avalonCannon:getDurability() <= 0 then
        trace("AvalonCannonM", "耐久度为0，不能使用了。");
        return false;
    end

    -- 如果已经自动填弹，那么直接可以使用
    if avalonCannon:queryIsLoad() == 1 then
        return true;
    end

    -- 负担不起消耗
    local ok = true;
    local cost = query(avalonCannon:query("classId"), "attack_cost");
    for key, amount in pairs(cost) do
        if type(key) == "string" then
            -- 属性
            ok = ME.user.dbase:query(key, 0) >= amount;
        elseif type(key) == "number" then
            -- 物品
            ok = ItemM.getAmount(ME.user, key) >= amount;
        else
            -- 暂无其他消耗
            ok = false;
        end

        if not ok then
            return "lack_of_material";
        end
    end

    return true;
end

-- 抽取攻击目标
function fetchTargetAndAttack()
    local avalonCannon = ME.user.avalon_cannon;
    local attackRange = avalonCannon:getAttackRange();

    -- 概率翻开格子
    local openProp = PropM.combine(avalonCannon, "avalon_attack_effect", "open_grid");

    -- 概率眩晕所有敌人
    local prop = PropM.combine(avalonCannon, "avalon_attack_effect", "spin");
    local isSpin = DungeonM.getRandSeed("avalon_attack_effect") % 1000 < prop[3];

    -- 随机选取一个目标
    local grid, monster;
    for _, pos in ipairs(attackRange) do
        grid = DungeonM.getGridByPos(pos);

        repeat
            -- 未翻开的格子
            if not grid:isOpened() then
                -- 翻开格子的概率，需要分开单独计算
                local isOpen = DungeonM.getRandSeed("avalon_attack_effect") % 1000 < openProp[3];
                if isOpen then
                    -- 延迟开格子
                    grid:open(false, true);
                else
                    break;
                end
            end

            -- 不是怪物
            monster = grid.monster;
            if not monster or grid.monster:isDead() then
                break;
            end

            -- 造成伤害
            cannonAttack(avalonCannon, monster, isSpin);

        until true;
    end
end

-- 攻击范围之内有没有敌人
function haveEnermyInRange()
    local avalonCannon = ME.user.avalon_cannon;
    local attackRange = avalonCannon:getAttackRange();

    local grids = {};
    for _, pos in pairs(attackRange) do
        local grid = DungeonM.getGridByPos(pos);
        if grid:isOpened() and grid:isMonster() and not grid.monster:isDead() then
            table.insert(grids, grid);
        end
    end

    return sizeof(grids) > 0;
end

-- 攻击消耗
function doCost()
    local avalonCannon = ME.user.avalon_cannon;
    local classId = avalonCannon.classId;

    local cost = query(classId, "attack_cost") or {};
    for key, amount in pairs(cost) do
        if type(key) == "number" then
            ItemM.costAmount(ME.user, key, amount);
        elseif type(key) == "string" then
            ME.user:costAttrib(key, amount);
        end
    end
end

-- 阿瓦隆巨炮攻击
function doAttack()
    -- 不满足攻击条件
    if canAttack() ~= true then
        return;
    end

    -- 先做消耗，如果已经自动填弹，那么就不消耗了
    local avalonCannon = ME.user.avalon_cannon;
    if avalonCannon:queryIsLoad() == 0 then
        doCost();
    end

    -- 统计存活的怪物数量
    local aliveMonsters = FormulaM.getAliveMonsters();
    local aliveCount = #table.keys(aliveMonsters);

    -- 对范围内的目标进行攻击
    fetchTargetAndAttack();

    -- 统计击杀的怪物数量
    aliveMonsters = FormulaM.getAliveMonsters();
    local killCount = aliveCount - #table.keys(aliveMonsters);

    -- 刷新冷却时间
    avalonCannon:setCd(avalonCannon:getMaxCountDown());

    local msg = string.format("阿瓦隆巨炮攻击，当前的冷却时间为：%d。\n", avalonCannon:getCountDown());
    DungeonLogM.addLog(msg);

    -- 如果是自动填弹的，那么标记已经使用
    if avalonCannon:queryIsLoad() == 1 then
        avalonCannon:setIsLoad(0);
    end

    -- 耐久度减1
    avalonCannon:setDurability(avalonCannon:getDurability() - 1);

    -- 尝试增加威力
    PropM.trigger(avalonCannon, "attack_add_force", 1);

    syncAvalonCannonData();

    -- 尝试摧毁巨炮
    tryDestory();

    -- 抛出巨炮攻击事件
    EventMgr.fire(event.AVALON_CANNON_ATTACK, { ["killCount"] = killCount, ["pos"] = avalonCannon:getPos(), });

    -- 一个回合事件
    EventMgr.fire(event.COMBAT_ROUND, { ["pos"] = avalonCannon:getPos(), });
end

-- 巨炮攻击(是点击发射之后的其中一个操作)
function cannonAttack(source, target, isSpin)
    -- 初始化行为序列
    SkillM.initSequence(source, target, 0);

    -- 计算闪避
    if not FormulaM.invoke("HAPPEN_DODGE", source, target, DungeonM.getRandSeed("HAPPEN_DODGE")) then
        -- 闪避不及，造成伤害
        -- 初始值等于巨炮的威力
        local damage = source:getAttack();

        -- 概率暴击
        local props = PropM.fetchProps(source, "probability_damage");
        for _, prop in ipairs(props) do
            damage = damage + PropM.trigger(source, prop[1], prop[2], damage);
        end

        -- 增加命中动作
        SkillM.getSequence(target):hit(source, target, skillId);

        -- 传入攻击者信息
        local assailantInfo = {
            ["grid_type"] = GRID_TYPE_AVALON_CANNON,
        };

        CombatM.receiveDamage(source, target, damage, 0, { assailantInfo = assailantInfo,});

        -- 如果需要眩晕敌人
        if isSpin then
            local condition = {["id"] = PropM.getPropId("spin"), ["end_round"] = CombatM.getRound() + 4,}
            CombatStatusM.applyStatus(target, "spin", condition);
        end
    else
        -- 闪避掉了
        DungeonLogM.addLog(string.format("%s闪避,闪避率%d,命中率%d", target:getName(), target:getDodge(), source:getAccuracy()));
        SkillM.getSequence(target):dodge(source, target);
    end
end

-- 尝试自动装填弹药
function tryLoadAmmunition()
    local avalonCannon = ME.user.avalon_cannon;
    if not avalonCannon then
        return false;
    end

    -- 如果存在飞艇控制台，那么不装填弹药
    local grid = AvalonFortM.findAirshipConsole();
    if grid then
        return false;
    end

    -- 如果已经填弹
    if avalonCannon:queryIsLoad() == 1 then
        return false;
    end

    local rand = DungeonM.getRandSeed("avalon_cannon_load_ammunition") % 1000;
    local rate = avalonCannon:getLoadRate();

    -- 标识自动装填弹药
    if rate > rand then
        avalonCannon:setIsLoad(1);
        setCannonLoadEffect(true);
    end

    syncAvalonCannonData();

    return true;
end

-- 尝试摧毁巨炮
function tryDestory()
    local avalonCannon = ME.user.avalon_cannon;

    -- 检查耐久度
    local durability = avalonCannon:getDurability();
    if durability > 0 then
        return;
    end

    -- 摧毁阿瓦隆巨炮
    avalonCannon:destory();

    -- 将格子设置为无效
    local pos = avalonCannon:getPos();
    local grids = DungeonM.getCurrentDungeon();
    grids[pos]:changeState(GRID_STATE_DISABLE);

    syncAvalonCannonData();

    -- 抛出巨炮消失事件
    EventMgr.fire(event.AVALON_CANNON_DESTORY, { ["pos"] = avalonCannon:getPos(), });
end

-- 设置阿瓦隆巨炮需要播放填弹光效
function setCannonLoadEffect(flag)
    playCannonLoadEffect = flag;
end

-- 查询阿瓦隆巨炮是否需要播放填弹光效
function queryCannonPlayLoadEffect()
    return playCannonLoadEffect;
end

-- 设置阿瓦隆巨炮的技能
function setCannonSkills(skills)
    avalonCannonSkills = skills;

    -- 如果有阿瓦隆巨炮，那么刷新下他的技能
    local avalonCannon = ME.user.avalon_cannon;
    if avalonCannon then
        avalonCannon:setSkills(avalonCannonSkills);

        syncAvalonCannonData();

        -- 刷新下属性
        PropM.refresh(avalonCannon);

        -- 刷新下冷却时间
        refreshCannonCd(avalonCannon);
    end
end

-- 刷新阿瓦隆巨炮冷却时间
function refreshCannonCd(avalonCannon)
    local cd = avalonCannon:query("cd") or 0;

    local lastMaxCd = avalonCannon:query("last_max_cd");
    local curMaxCd = avalonCannon:getMaxCountDown();


    if lastMaxCd ~= curMaxCd then
        cd = math.max(0, cd - (lastMaxCd - curMaxCd));
        avalonCannon.dbase:set("last_max_cd", curMaxCd);

        avalonCannon:setCd(cd);
    end
end

-- 获取阿瓦隆巨炮的技能
function getCannonSkills()
    return avalonCannonSkills;
end

-- 获取阿瓦隆巨炮的数据
function fetchAvalonCannonData(classId)
    local avalonCannon = ME.user.dbase:query("avalon_cannon");

    -- 填充进技能信息
    local para = avalonCannonSkills or {};

    -- 阿瓦隆巨炮的信息
    return table.add({
        ["class_id"] = classId,
    }, avalonCannon or para);
end

-- 同步数据到dbase下
function syncAvalonCannonData()
    local avalonCannon = ME.user.avalon_cannon;
    if avalonCannon then
        ME.user.dbase:set("avalon_cannon", avalonCannon:getSaveData());
    end
end
